//
//  UncaughtExceptionHandler.m
//  UncaughtExceptions
//
//  Created by Matt Gallagher on 2010/05/25.
//  Copyright 2010 Matt Gallagher. All rights reserved.
//
//  Permission is given to use this source code file, free of charge, in any
//  project, commercial or otherwise, entirely at your risk, with the condition
//  that any redistribution (in part or whole) of source code must retain
//  this copyright and permission notice. Attribution in compiled projects is
//  appreciated but not required.
//

#import "UncaughtExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#import "SKPSMTPMessage.h"
#include <sys/utsname.h>
#import "Base64.h"

NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";

volatile int32_t UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;

const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;

@implementation UncaughtExceptionHandler

+ (NSArray *)backtrace
{
	 void* callstack[128];
	 int frames = backtrace(callstack, 128);
	 char **strs = backtrace_symbols(callstack, frames);
	 
	 int i;
	 NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
	 for (
	 	i = UncaughtExceptionHandlerSkipAddressCount;
	 	i < UncaughtExceptionHandlerSkipAddressCount +
			UncaughtExceptionHandlerReportAddressCount;
		i++)
	 {
	 	[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
	 }
	 free(strs);
	 
	 return backtrace;
}

- (void)validateAndSaveCriticalApplicationData
{
	
}

#pragma mark ------------ 异常处理和提示
- (void)handleException:(NSException *)exception
{
	[self validateAndSaveCriticalApplicationData];
	UIAlertView *alert =
		[[UIAlertView alloc]
			initWithTitle:@"This is flase"
			message:@"We need you help"
			delegate:self
			cancelButtonTitle:@"Cancel"
			otherButtonTitles:@"Send", nil];
	[alert show];
	
	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
	CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
	
	while (!dismissed)
	{
		for (NSString *mode in (__bridge NSArray *)allModes)
		{
			CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
		}
	}
	
	CFRelease(allModes);

	NSSetUncaughtExceptionHandler(NULL);
	signal(SIGABRT, SIG_DFL);
	signal(SIGILL, SIG_DFL);
	signal(SIGSEGV, SIG_DFL);
	signal(SIGFPE, SIG_DFL);
	signal(SIGBUS, SIG_DFL);
	signal(SIGPIPE, SIG_DFL);
	
	if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName])
	{
		kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
	}
	else
	{
		[exception raise];
	}
}

#pragma mark ---------- 用户确定的时候提交
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
    if (anIndex == 1) {
        [self sendMessage];
    }
}


#pragma mark ----------- 把异常写入文件
-(void)WriteInfoToFiles:(NSException *)exception
{
    NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\nUDID : %@\n",
                         
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
                         
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
                         
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
                         
                         [self deviceModel],
                         
                         [UIDevice currentDevice].systemName,
                         
                         [UIDevice currentDevice].systemVersion,
                         
                         [UIDevice currentDevice].identifierForVendor];
    
    //获取堆栈信息
    NSArray * stackArray = [exception callStackSymbols];
    //出现异常的原因
    NSString * reason = [exception reason];
    //异常名称
    NSString * name = [exception name];
    
    NSString * exceptionInfo = [NSString stringWithFormat:@"AppInfo:%@\n Exception reason:%@\n Exception name:%@\n Exception stack:%@",appInfo,reason,name,stackArray];
    NSMutableArray * tmpArr = [NSMutableArray arrayWithArray:stackArray];
    [tmpArr insertObject:reason atIndex:0];
    NSString * filePath = [NSString stringWithFormat:@"%@/Library/Caches/error.log",NSHomeDirectory()];
    [exceptionInfo writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}


#pragma mark ---------- 发送异常到邮箱
-(void)sendMessage
{
        NSString *vcfPath = [NSString stringWithFormat:@"%@/Library/Caches/error.log",NSHomeDirectory()];
//        NSFileManager * fileManager = [NSFileManager defaultManager];
        SKPSMTPMessage *testMsg = [[SKPSMTPMessage alloc] init];
        testMsg.fromEmail = @"18674524119@163.com";
        testMsg.toEmail =@"18674524119@163.com";
        testMsg.relayHost = @"smtp.163.com";
        testMsg.requiresAuth = YES;
        testMsg.login = @"18674524119@163.com";
        testMsg.pass = @"star891123@";
        testMsg.subject = [NSString stringWithCString:"测试" encoding:NSUTF8StringEncoding];
        NSDictionary *plainPart = [NSDictionary dictionaryWithObjectsAndKeys:@"text/plain",kSKPSMTPPartContentTypeKey,
                                   [NSString stringWithCString:"测试正文" encoding:NSUTF8StringEncoding],kSKPSMTPPartMessageKey,@"8bit",kSKPSMTPPartContentTransferEncodingKey,nil];
        NSData *vcfData = [NSData dataWithContentsOfFile:vcfPath];
        NSDictionary *vcfPart = [NSDictionary dictionaryWithObjectsAndKeys:@"text/directory;\r\n\tx-unix-mode=0644;\r\n\tname=\"error.log\"",kSKPSMTPPartContentTypeKey,
                                 @"attachment;\r\n\tfilename=\"error.log\"",kSKPSMTPPartContentDispositionKey,[vcfData base64EncodedString],kSKPSMTPPartMessageKey,@"base64",kSKPSMTPPartContentTransferEncodingKey,nil];
    
        testMsg.parts = [NSArray arrayWithObjects:plainPart,vcfPart,nil];
        [testMsg send];
}

- (NSString *)deviceModel{
    
    struct utsname systemInfo;
    
    uname(&systemInfo);
    
    NSString* code = [NSString stringWithCString:systemInfo.machine
                                        encoding:NSUTF8StringEncoding];
    
    static NSDictionary* deviceNamesByCode = nil;
    
    if (!deviceNamesByCode) {
        
        deviceNamesByCode = @{
                              @"iPhone3,1" : @"Device_iPhone4",
                              @"iPhone3,2" : @"Device_iPhone4",
                              @"iPhone3,3" : @"Device_iPhone4",
                              @"iPhone4,1" : @"Device_iPhone4S",
                              @"iPhone5,1" : @"Device_iPhone5",
                              @"iPhone5,2" : @"Device_iPhone5",
                              @"iPhone5,3" : @"Device_iPhone5C",
                              @"iPhone5,4" : @"Device_iPhone5C",
                              @"iPhone6,1" : @"Device_iPhone5S",
                              @"iPhone6,2" : @"Device_iPhone5S",
                              @"iPhone7,1" : @"Device_iPhone6plus",
                              @"iPhone7,2" : @"Device_iPhone6",
                              @"iPhone8,1" : @"Device_iPhone6S",
                              @"iPhone8,2" : @"Device_iPhone6Splus"
                              };
    }
    
    NSString* deviceName = [deviceNamesByCode objectForKey:code];
    if(deviceName){
        return deviceName;
    }
    
    return @"新机器>6s";
}

@end

void HandleException(NSException *exception)
{
    [[[UncaughtExceptionHandler alloc] init]
     performSelectorOnMainThread:@selector(WriteInfoToFiles:)
     withObject:exception
     waitUntilDone:YES];
    
	int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
	if (exceptionCount > UncaughtExceptionMaximum)
	{
		return;
	}
	
	NSArray *callStack = [UncaughtExceptionHandler backtrace];
	NSMutableDictionary *userInfo =
		[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
	[userInfo
		setObject:callStack
		forKey:UncaughtExceptionHandlerAddressesKey];
	
	[[[UncaughtExceptionHandler alloc] init]
		performSelectorOnMainThread:@selector(handleException:)
		withObject:
			[NSException
				exceptionWithName:[exception name]
				reason:[exception reason]
				userInfo:userInfo]
		waitUntilDone:YES];
}

void SignalHandler(int signal)
{
	int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
	if (exceptionCount > UncaughtExceptionMaximum)
	{
		return;
	}
	
	NSMutableDictionary *userInfo =
		[NSMutableDictionary
			dictionaryWithObject:[NSNumber numberWithInt:signal]
			forKey:UncaughtExceptionHandlerSignalKey];

	NSArray *callStack = [UncaughtExceptionHandler backtrace];
	[userInfo
		setObject:callStack
		forKey:UncaughtExceptionHandlerAddressesKey];
	
	[[[UncaughtExceptionHandler alloc] init]
		performSelectorOnMainThread:@selector(handleException:)
		withObject:
			[NSException
				exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
				reason:
					[NSString stringWithFormat:
						NSLocalizedString(@"Signal %d was raised.", nil),
						signal]
				userInfo:
					[NSDictionary
						dictionaryWithObject:[NSNumber numberWithInt:signal]
						forKey:UncaughtExceptionHandlerSignalKey]]
		waitUntilDone:YES];
}

void InstallUncaughtExceptionHandler(void)
{
	NSSetUncaughtExceptionHandler(&HandleException);
	signal(SIGABRT, SignalHandler);
	signal(SIGILL, SignalHandler);
	signal(SIGSEGV, SignalHandler);
	signal(SIGFPE, SignalHandler);
	signal(SIGBUS, SignalHandler);
	signal(SIGPIPE, SignalHandler);
}

